#include "engine.h"
#include "input.h"
#include "3rdparty/Leo/LeoAPI.h"

/*#ifdef WIN32
#include <Windows.h>
#include <dinput.h>
#endif*/

#ifdef _XBOX
#include <Xinput2.h>
#else
#include <Xinput.h>
#endif


#ifdef LEO_DESIGNER
void merge_gamepad_state(gamepad_t::state_t &target,const gamepad_t::state_t &source) {
	target.button_state|=source.button_state;
	const int numaxis=sizeof(source.axis_state)/sizeof(source.axis_state[0]);
	for (int i=0;i<numaxis;++i) {
		const int srcaxisdist=abs(source.axis_state[i]);
		const int trgaxisdist=abs(target.axis_state[i]);
		
		if (trgaxisdist<srcaxisdist)
			target.axis_state[i]=source.axis_state[i];
	};
};
#endif


keyboard_t::keyboard_t()
{
#ifdef WIN32
	memset(keyboard_state,0,256);
	memset(prev_keyboard_state,0,256);
#endif
}

bool keyboard_t::key_pressed(int index) const
{
#ifdef WIN32
	return keyboard_state[index]!=0 && prev_keyboard_state[index]==0;
#else
	return false;
#endif
}
bool keyboard_t::key_released(int index) const
{
#ifdef WIN32
	return keyboard_state[index]==0 && prev_keyboard_state[index]!=0;
#else
	return false;
#endif
}
bool keyboard_t::key_down(int index) const
{
#ifdef WIN32
	return keyboard_state[index]!=0;
#else
	return false;
#endif
}

void keyboard_t::update()
{
#ifdef WIN32
	if (device)
	{
		HRESULT hr = device->Poll();

		if (FAILED(hr)) 
		{
			hr = device->Acquire();
			ZeroMemory(&keyboard_state,sizeof(keyboard_state));
			ZeroMemory(&prev_keyboard_state,sizeof(prev_keyboard_state));
		}
		else
		{
			memcpy(prev_keyboard_state,keyboard_state,256);
			/*HRESULT hr = */device->GetDeviceState(256,keyboard_state);
		}
	}
#endif
	if (!INPSYS->mouse_and_keyboard_enabled)
	{
		ZeroMemory(&keyboard_state,sizeof(keyboard_state));
		ZeroMemory(&prev_keyboard_state,sizeof(prev_keyboard_state));
		return;
	}
}

bool leodevice_t::device_present=false;

leodevice_t::leodevice_t()
{
	birdpos.set(0,0,0);
	birdrot.set(0,0,0);
//	device_present=leoInitialize();
}

leodevice_t::~leodevice_t()
{
}

bool leodevice_t::small_button_down() const
{
	if (device_present)
	{
		return leoGetSmallButtonState();
	}

	return false;
}
bool leodevice_t::big_button_down() const
{
	if (device_present)
	{
		return leoGetBigButtonState()>0.5f;
	}

	return false;
}

void leodevice_t::set_vibration(float val)
{
	if (device_present)
		leoSetBirdVibration(val);
}

void leodevice_t::update()
{
	if (device_present)
	{
		double bp[6]={0,0,0,0,0,0};
		leoGetBirdPosition(bp+0,bp+1,bp+2,bp+3,bp+4,bp+5);

		birdpos.x=(float)bp[0];
		birdpos.y=(float)bp[1];
		birdpos.z=-(float)bp[2];
		birdrot.x=(float)bp[3];
		birdrot.y=-(float)bp[4];
		birdrot.z=(float)bp[5];

		if (INPSYS->keyboard.key_pressed(DIK_S))
		{
			leoSetStereo(!leoIsStereoSwitchedOn());
		}
	}
}

float3 leodevice_t::get_bird_pos() const
{
	return birdpos;
}

float3 leodevice_t::get_bird_rot() const
{
	return birdrot;
}

float4x4 leodevice_t::get_bird_mtx() const
{
	float4x4 mtx;
	mtx.identity();
	mtx.axis(3)=birdpos;
	mtx.from_rotation(birdrot.y,birdrot.x,birdrot.z);

	return mtx;
}

bool gamepad_t::button_pressed(int index) const
{
	return (state.button_state & (1 << index)) && !(prev_state.button_state & (1 << index));
}
bool gamepad_t::button_released(int index) const
{
	return !(state.button_state & (1 << index)) && (prev_state.button_state & (1 << index));
}

bool gamepad_t::axis_pressed_up(int index) const
{
	return axis(index)>0.6f && prev_axis(index)<=0.6f;
}

bool gamepad_t::axis_pressed_down(int index) const
{
	return axis(index)<-0.6f && prev_axis(index)>=-0.6f;
}

float gamepad_t::button(int index) const
{
	return (state.button_state & (1 << index)) ? 1.0f : 0.0f;
}

#define AXIS_DEADZONE 0.2f
#define DEADZONING(x) ((x>-AXIS_DEADZONE) && (x<AXIS_DEADZONE) ? 0 : (x>0 ? (x-AXIS_DEADZONE)/(1.0f-AXIS_DEADZONE) : (x+AXIS_DEADZONE)/(1.0f-AXIS_DEADZONE)))
float gamepad_t::axis(int index) const
{
	return DEADZONING(((float)(state.axis_state[index])+0.5f)/127.5f);
}

float gamepad_t::prev_axis(int index) const
{
	return DEADZONING(((float)(prev_state.axis_state[index])+0.5f)/127.5f);
}
#ifdef LEO_DESIGNER
	bool g_need_vibration=false;
#endif

void gamepad_t::set_vibration(float v_l, float v_r)
{
#ifdef LEO_DESIGNER
	if (!g_need_vibration)
		return;
#endif
	XINPUT_VIBRATION v;
	v.wLeftMotorSpeed=(WORD)(v_l*65535.0f);
	v.wRightMotorSpeed=(WORD)(v_r*65535.0f);
	XInputSetState(pad_index,&v);
}

void gamepad_t::set_vibration(float v)
{
#ifdef LEO_DESIGNER
	if (!g_need_vibration)
		return;
#endif
	XINPUT_VIBRATION xv;
	xv.wLeftMotorSpeed=(WORD)(v*65535.0f);
	xv.wRightMotorSpeed=(WORD)(v*65535.0f);
	XInputSetState(pad_index,&xv);
}

void gamepad_t::init(int idx)
{
	pad_index=idx;
	state.connected=false;
	prev_state.connected=false;
}

void gamepad_t::reset()
{
	const bool oldconnected=state.connected;
	const bool old_prev_connected=prev_state.connected;

	ZeroMemory(&state,sizeof(state));
	ZeroMemory(&prev_state,sizeof(prev_state));
	set_vibration(0.0f);

	state.connected=oldconnected;
	prev_state.connected=old_prev_connected;
}

#define IDLE_AXIS_DEADZONE 30

bool gamepad_t::is_idle() const
{
	if (!state.connected)
		return true;

	if (state.button_state)
		return false;

	for (int i=0;i<INPUT_NUMAXIS;++i)
		if (state.axis_state[i]<-IDLE_AXIS_DEADZONE || IDLE_AXIS_DEADZONE<state.axis_state[i])
			return false;

	return true;
}


//mouse
mouse_t::mouse_t()
{
	ZeroMemory(&mouse_state,sizeof(mouse_state));
	ZeroMemory(&prev_mouse_state,sizeof(prev_mouse_state));
	shown=true;
}

void mouse_t::hide()
{
	if (shown)
	{
		shown=false;
		device->Unacquire();
		device->SetCooperativeLevel(INPSYS->hwnd, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
		device->Acquire();
	}
}
void mouse_t::show()
{
	if (!shown)
	{
		shown=true;
		device->Unacquire();
		device->SetCooperativeLevel(INPSYS->hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
		device->Acquire();
	}
}

void mouse_t::update()
{
	if (device)
	{
		HRESULT hr = device->Poll();
		if (FAILED(hr)) 
		{
			hr = device->Acquire();
		}
		else
		{
			prev_mouse_state=mouse_state;
			hr = device->GetDeviceState(sizeof(DIMOUSESTATE2), &mouse_state);

			for (int n=0; n<8; ++n)
			{
				mouse_state.rgbButtons[n]=(mouse_state.rgbButtons[n] & 0x80) >> 7;
			}
		}
	}
	if (!INPSYS->mouse_and_keyboard_enabled)
	{
		ZeroMemory(&mouse_state,sizeof(mouse_state));
		ZeroMemory(&prev_mouse_state,sizeof(prev_mouse_state));
		return;
	}

}








inputsystem_t::inputsystem_t(HWND hwnd)
{
	this->hwnd=hwnd;
#ifdef WIN32
	DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION,IID_IDirectInput8, (void**)&hw_input, NULL);

	//keyboard
	LPDIRECTINPUTDEVICE8 hw_keyboard;
	hw_input->CreateDevice(GUID_SysKeyboard, &hw_keyboard, NULL);

	if (hw_keyboard)
	{
		hw_keyboard->SetDataFormat(&c_dfDIKeyboard);
		hw_keyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
		hw_keyboard->Acquire();
		keyboard.device=hw_keyboard;
	}
	//keyboard
	LPDIRECTINPUTDEVICE8 hw_mouse;
	hw_input->CreateDevice(GUID_SysMouse, &hw_mouse, NULL);

	if (hw_mouse)
	{
		hw_mouse->SetDataFormat(&c_dfDIMouse2);
		hw_mouse->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
		hw_mouse->Acquire();
		mouse.device=hw_mouse;
	}


#endif

	for (int i=0;i<INPUT_NUMMAXGAMEPADS;++i)
		gamepad[i].init(i);

#ifdef LEO_DESIGNER
	editor_gamepad.pad_index=0;
#endif

	reset();
	mouse_and_keyboard_enabled=true;
}

void inputsystem_t::enable_mouse_and_keyboard(bool enable)
{
	mouse_and_keyboard_enabled=enable;
}


inputsystem_t::~inputsystem_t()
{
#ifdef WIN32
	if (keyboard.device)
	{
		keyboard.device->Unacquire();
		keyboard.device->Release();
	}

	if (hw_input)
		hw_input->Release();
#endif
}

void inputsystem_t::reset()
{
	//gamepad
	for (int n=0; n<INPUT_NUMMAXGAMEPADS; ++n)
	{
		gamepad[n].reset();
	}

}

void inputsystem_t::update()
{
#ifdef WIN32
	keyboard.update();
	leodevice.update();
	mouse.update();
#endif

	//gamepad
	for (int n=0; n<INPUT_NUMMAXGAMEPADS; ++n)
	{
		gamepad[n].prev_state=gamepad[n].state;
		XINPUT_STATE s;
		if (ERROR_SUCCESS==XInputGetState(n,&s))
		{
			gamepad[n].state.connected=true;

			gamepad[n].state.axis_state[0]=(s.Gamepad.sThumbLX >> 8);
			gamepad[n].state.axis_state[1]=(s.Gamepad.sThumbLY >> 8);
			gamepad[n].state.axis_state[2]=(s.Gamepad.sThumbRX >> 8);
			gamepad[n].state.axis_state[3]=(s.Gamepad.sThumbRY >> 8);
			gamepad[n].state.axis_state[4]=s.Gamepad.bLeftTrigger;
			gamepad[n].state.axis_state[5]=s.Gamepad.bRightTrigger;
			gamepad[n].state.button_state=s.Gamepad.wButtons;

			if (s.Gamepad.bLeftTrigger>127)
				gamepad[n].state.button_state|=(1 << BUTTON_LEFT_TRIGGER);

			if (s.Gamepad.bRightTrigger>127)
				gamepad[n].state.button_state|=(1 << BUTTON_RIGHT_TRIGGER);
		}
		else
		{
			ZeroMemory(&gamepad[n].state,sizeof(gamepad[n].state));
		}
	}

#ifdef LEO_DESIGNER
	ZeroMemory(&editor_gamepad.state,sizeof(editor_gamepad.state));
	ZeroMemory(&editor_gamepad.prev_state,sizeof(editor_gamepad.prev_state));
	for (int i=0;i<INPUT_NUMMAXGAMEPADS;++i) {
		merge_gamepad_state(editor_gamepad.state,gamepad[i].state);
		merge_gamepad_state(editor_gamepad.prev_state,gamepad[i].prev_state);
	};
	editor_gamepad.state.connected=true;
	editor_gamepad.prev_state.connected=true;
#endif
}

void inputsystem_t::init(HWND hwnd)
{
	INPSYS=new inputsystem_t(hwnd);
}

void inputsystem_t::exit()
{
	delete INPSYS;
	INPSYS=0;
}



//script wrapper functions
bool _cdecl button_pressed(int game_pad, int id)
{
	return INPSYS->get_gamepad(game_pad)->button_pressed(id);
}

bool _cdecl button_released(int game_pad, int id)
{
	return INPSYS->get_gamepad(game_pad)->button_released(id);
}

bool _cdecl button_down(int game_pad, int id)
{
	return INPSYS->get_gamepad(game_pad)->button(id)>0.5f;
}

float _cdecl axis(int game_pad, int id)
{
	return INPSYS->get_gamepad(game_pad)->axis(id);
}

bool axis_pressed(int game_pad, int id, int dir)
{
	if (dir>0)
		return INPSYS->get_gamepad(game_pad)->axis_pressed_up(id);

	return INPSYS->get_gamepad(game_pad)->axis_pressed_down(id);
}

void reset_input()
{
	INPSYS->reset();
}

int getmaxnumberofgamepads() {
	return INPUT_NUMMAXGAMEPADS;
}

float3 get_bird_pos()
{
	return INPSYS->leodevice.get_bird_pos();
}

float3 get_bird_rot()
{
	return INPSYS->leodevice.get_bird_rot();
}

float4x4 get_bird_matrix()
{
	return INPSYS->leodevice.get_bird_mtx();
}

bool bird_small_button_down()
{

	return INPSYS->leodevice.small_button_down();
}

bool bird_big_button_down()
{

	return INPSYS->leodevice.big_button_down();
}

bool mouse_button_down(int b)
{
	return INPSYS->mouse.button_down(b);
}

bool mouse_button_pressed(int b)
{
	return INPSYS->mouse.button_pressed(b);
}

float3 mouse_pos()
{
	return INPSYS->mouse.get_mouse_pos();
}

bool key_down(int keycode)
{
	return INPSYS->keyboard.key_down(keycode);
}

void hide_mouse()
{
	INPSYS->mouse.hide();
}
void show_mouse()
{
	INPSYS->mouse.show();
}

void set_bird_vibration(float v)
{
	INPSYS->leodevice.set_vibration(v);
}

#include "script/script.h"
script_fn_registering_begin(input)
register_script_fn("float3 get_bird_pos()",get_bird_pos);
register_script_fn("float3 get_bird_rot()",get_bird_rot);
register_script_fn("float4x4 get_bird_matrix()",get_bird_matrix);
register_script_fn("bool bird_small_button_down()", bird_small_button_down);
register_script_fn("bool bird_big_button_down()", bird_big_button_down);
register_script_fn("void set_bird_vibration(float)", set_bird_vibration);

register_script_fn("bool mouse_button_down(int)", mouse_button_down);
register_script_fn("bool mouse_button_pressed(int)", mouse_button_pressed);
register_script_fn("bool key_down(int)", key_down);
register_script_fn("float3 mouse_pos()", mouse_pos);
register_script_fn("void hide_mouse()", hide_mouse);
register_script_fn("void show_mouse()", show_mouse);

script_fn_registering_end

inputsystem_t* INPSYS=NULL;
